package com.skillworld.webapp.model.courseservice;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.skillworld.webapp.model.award.AwardDao;
import com.skillworld.webapp.model.course.Course;
import com.skillworld.webapp.model.course.Course.Status;
import com.skillworld.webapp.model.course.CourseDao;
import com.skillworld.webapp.model.department.Department;
import com.skillworld.webapp.model.department.DepartmentDao;
import com.skillworld.webapp.model.department.rank.DeptRank;
import com.skillworld.webapp.model.enrollment.Enrollment;
import com.skillworld.webapp.model.enrollment.EnrollmentDao;
import com.skillworld.webapp.model.enrollment.Examination;
import com.skillworld.webapp.model.enrollment.ExaminationDao;
import com.skillworld.webapp.model.enrollment.ReleasedExam;
import com.skillworld.webapp.model.enrollment.ReleasedExam.Grade;
import com.skillworld.webapp.model.enrollment.ReleasedExam.State;
import com.skillworld.webapp.model.language.LanguageDao;
import com.skillworld.webapp.model.language.LanguageNotValidException;
import com.skillworld.webapp.model.languageservice.LanguageService;
import com.skillworld.webapp.model.lesson.Exam;
import com.skillworld.webapp.model.lesson.ExamDao;
import com.skillworld.webapp.model.lesson.Lesson;
import com.skillworld.webapp.model.lesson.LessonDao;
import com.skillworld.webapp.model.tutorial.MarkedTutorial.Mark;
import com.skillworld.webapp.model.tutorial.MarkedTutorialDao;
import com.skillworld.webapp.model.tutorial.Tutorial;
import com.skillworld.webapp.model.tutorial.TutorialDao;
import com.skillworld.webapp.model.tutorialservice.Query;
import com.skillworld.webapp.model.tutorialservice.TutorialService;
import com.skillworld.webapp.model.user.User;
import com.skillworld.webapp.model.user.UserDao;
import com.skillworld.webapp.model.util.AssignAward;
import com.skillworld.webapp.model.util.InvalidArgumentException;
import com.skillworld.webapp.model.util.PermissionDeniedException;

import es.udc.pojo.modelutil.exceptions.InstanceNotFoundException;

@Service("CourseService")
@Transactional
public class CourseServiceImpl implements CourseService
{
    private final Random random = new Random();
    
    @Autowired
    private CourseDao courseDao;
    
    @Autowired
    private ExamDao examDao;
    
    @Autowired
    private DepartmentDao departmentDao;
    
    @Autowired
    private UserDao userDao;
    
    @Autowired
    private AwardDao awardDao;   
    
    @Autowired
    private LessonDao lessonDao;
    
    @Autowired
    private TutorialDao tutorialDao;

    @Autowired
    private MarkedTutorialDao markedTutorialDao;
    
    @Autowired
    private TutorialService tutorialService;
    
    @Autowired
    private LanguageDao languageDao;
    
    @Autowired
    private EnrollmentDao enrollmentDao;
    
    @Autowired
    private ExaminationDao examinationDao;
    
    @Autowired
    private LanguageService languageService;


    @Transactional(readOnly = true)
    public List<Course> searchCourses(String query, Long dept, 
                                        Locale locale, Long author,
                                        Status status,
                                        int startIndex, int count)
    {
        List<String> keywords = Query.parseQuery(query);
        return courseDao.search(keywords, dept, locale, author, status, 
                                startIndex, count);
    }
    
    @Transactional(readOnly = true)
    public int numberOfCourses(String query, Long dept, 
                                Locale locale, Long author,
                                Status status)
    {
        List<String> keywords = Query.parseQuery(query);
        return(int) courseDao.numberOfCourses(keywords, dept, locale, author, status);
    }
    
    @Transactional(readOnly = true)
    public Course findCourse(long courseId) throws InstanceNotFoundException
    {
        return courseDao.findById(courseId);
    }
    
    @Transactional(readOnly = true)
    public CourseInfo viewCourse(long courseId, long userId)
            throws InstanceNotFoundException
    {
        Course course = courseDao.findById(courseId);
        Enrollment enrollment;
        
        try 
        { enrollment = enrollmentDao.find(courseId, userId); }
        catch(InstanceNotFoundException e)
        { enrollment = null; }
        
        return new CourseInfo(course, enrollment);
    }
    
    @Transactional(readOnly = true)
    public Lesson findLesson(long lessonId)
            throws InstanceNotFoundException
    {
        return lessonDao.findById(lessonId);
    }
    
    @Transactional(readOnly = true)
    public LessonInfo viewLesson(long lessonId, long userId)
            throws InstanceNotFoundException
    {
        Lesson lesson = lessonDao.findById(lessonId);
        Examination examination;
        
        try 
        { examination = examinationDao.find(lessonId, userId); }
        catch(InstanceNotFoundException e)
        { examination = null; }
        
        return new LessonInfo(lesson, examination);
    }
    
    @Transactional(readOnly = true)
    public List<Exam> listExams(long userId, long courseId, 
                                List<Long> lessons, 
                                int startIndex, int count) 
            throws InstanceNotFoundException, PermissionDeniedException
    { 
        if(!isCourseMaster(userId, courseId))
        { throw new PermissionDeniedException(); }
        
        return examDao.findExams(courseId, lessons, startIndex, count);
    }
    
    @Transactional(readOnly = true)
    public int numberOfExams(long userId, long courseId, 
                                List<Long> lessons) 
            throws InstanceNotFoundException, PermissionDeniedException
    { 
        if(!isCourseMaster(userId, courseId))
        { throw new PermissionDeniedException(); }
            
        return(int) examDao.numberOfExams(courseId, lessons);
    }
    
    public void applyCourse(Long courseId, Long userId) 
            throws InstanceNotFoundException, PermissionDeniedException,
                    AlreadyAnStudentException, NoVacanciesLeftException
    {
        User user = userDao.findById(userId);
        Course course = courseDao.findById(courseId);
        
        if(course.getAuthor() == user)
        { throw new PermissionDeniedException(); }
        
        if(!course.hasVacancies())
        { throw new NoVacanciesLeftException(courseId); }
        
        if(enrollmentDao.exists(courseId,userId))
        { throw new AlreadyAnStudentException(courseId, userId); }
        
        Enrollment enrollment = new Enrollment(course, user);
        enrollmentDao.save(enrollment);
        course.newEnrollment();
        
        new AssignAward(course.getAuthor(),awardDao)
            .addUserAwardByNumStudents();
    }
    
    public Course createCourse(String name, String description, 
                                Locale locale, int maxStudents, 
                                long userId, long departmentId)
            throws InstanceNotFoundException, PermissionDeniedException 
    {
        User author = userDao.findById(userId);
        Department department = departmentDao.findById(departmentId);
              
        if(!author.getDepartmentRank(department).isMasterCoach())
        { throw new PermissionDeniedException(); }
        
        Course course = new Course(locale, name, description, 
                                    author, department, maxStudents);
        courseDao.save(course);
        
        return course; 
    }

    /*
     * TODO For now, we can ignore the minimum % of own tutorials...
     */
    public Lesson createLesson(long courseId, long authorId, 
                                String name, String description, 
                                List<Long> tutorialsIds) 
            throws InstanceNotFoundException, PermissionDeniedException
    {
        Course course = courseDao.findById(courseId);
        User author = userDao.findById(authorId);
        
        if(course.isEnabled())
        { throw new PermissionDeniedException(); }
             
        if(course.getAuthor() != author)
        { throw new PermissionDeniedException(); }

        // TODO FIX this is extremely inefficient
        List<Tutorial> tutorials = new ArrayList<Tutorial>();
        for(long tutorialId : tutorialsIds)
        {
            Tutorial t = tutorialDao.findById(tutorialId);
            
            if(t.getDepartment() != course.getDepartment())
            { throw new PermissionDeniedException(); }
            
            tutorials.add(t);
        }
               
        Lesson lesson = new Lesson(course, name, description, tutorials);
        lessonDao.save(lesson);
        
        return lesson;
    }
    
    /*
     * TODO For now, we can ignore the minimum % of own tutorials...
     */
    public void addTutorials(long userId, long lessonId, List<Long> tutorialsIds) 
            throws InstanceNotFoundException, PermissionDeniedException
    {
        User user = userDao.findById(userId);
        Lesson lesson = lessonDao.findById(lessonId);
        
        if(lesson.getCourse().getAuthor() != user)
        { throw new PermissionDeniedException(); }
   
        List<Tutorial> tutorials = new ArrayList<Tutorial>();
        for(long tutorialId : tutorialsIds)
        {
            Tutorial t = tutorialDao.findById(tutorialId);
            
            if(t.getDepartment() != lesson.getCourse().getDepartment()) 
            { throw new PermissionDeniedException(); }
            
            tutorials.add(t);
        }
        
        lesson.getTutorials().addAll(tutorials);
    }

    public void removeTutorial(long userId, long lessonId, long tutorialId) 
            throws InstanceNotFoundException, PermissionDeniedException,
                    TutorialNotInLessonException 
    {
        User user = userDao.findById(userId);
        Lesson lesson = lessonDao.findById(lessonId);
        Tutorial tutorial = tutorialDao.findById(tutorialId);
        
        if(lesson.getCourse().getAuthor() != user)
        { throw new PermissionDeniedException(); }
        
        if(lesson.getCourse().getStatus() != Status.PENDING)
        { throw new PermissionDeniedException(); }
        
        if(!lesson.getTutorials().contains(tutorial))
        { throw new TutorialNotInLessonException(tutorialId,lessonId); }
        
        lesson.getTutorials().remove(tutorial);
    }
    
    public Exam createExam(long userId, long lessonId, String fileURL) 
            throws InstanceNotFoundException, PermissionDeniedException
    { 
        User user = userDao.findById(userId);
        Lesson lesson = lessonDao.findById(lessonId);
        
        if(lesson.getCourse().getAuthor() != user)
        { throw new PermissionDeniedException(); }   
        
        Exam exam = new Exam(lesson, fileURL);
        examDao.save(exam);
        
        return exam;
    }
    
    public void finishCourse(long userId, long courseId) 
            throws InstanceNotFoundException, NotAMasterOfCourseException, 
                    CourseAlreadyEnabledException, LessonsRequiredException, 
                    LessonWithoutTutorialsException, LessonWithoutExamsException 
                    
    {
        User master = userDao.findById(userId);
        Course course = courseDao.findById(courseId);
        
        if(course.getAuthor() != master)
        { throw new NotAMasterOfCourseException(master.getUserId(),course.getCourseId()); }
        
        if(course.getStatus() != Course.Status.PENDING)
        { throw new CourseAlreadyEnabledException(courseId); }
        
        List<Lesson> lessons = course.getLessons();
        
        if(lessons.isEmpty())
        { throw new LessonsRequiredException(courseId); }
        
        // Check lessons
        for(Lesson l : lessons)
        {
            if(l.getTutorials().isEmpty())
            { throw new LessonWithoutTutorialsException(l.getLessonId()); }
            
            if(l.getExams().isEmpty())
            { throw new LessonWithoutExamsException(l.getLessonId()); }
        }
        
        course.setStatus(Status.ENABLED);
        
        new AssignAward(course.getAuthor(), awardDao)
            .addUserAwardByNumCousesCreated();
    }
    
    @Transactional(readOnly = true)
    public Map<Calendar,Lesson> listLessons(Long idUser) throws InstanceNotFoundException {

      	List<Examination> list = examinationDao.findByUser(idUser); //Aqui tem a data de conclusão da lição e a própria lição
    	
      	Map<Calendar,Lesson> listMap = (Map<Calendar, Lesson>) list;   
      	
    	return listMap; 
    }

	public void removeLesson(Long userId, Long lessonId) throws InstanceNotFoundException, NotAMasterOfCourseException {
		User master = userDao.findById(userId);
		Lesson lesson = lessonDao.findById(lessonId);
		Course course = lesson.getCourse();
        
        if(course.getAuthor() != master){
        	throw new NotAMasterOfCourseException(userId, course.getCourseId());}
        
        course.getLessons().remove(lesson);
        courseDao.save(course); 
		
	}
	
    public void disableExam(long userId, long examId) 
            throws InstanceNotFoundException, NotAMasterOfCourseException, 
                    ExamAlreadyDisabledException
    {
        User master = userDao.findById(userId);
        Exam exam = examDao.findById(examId);
        Course course = exam.getLesson().getCourse();
        
        if(course.getAuthor() != master)
        { throw new NotAMasterOfCourseException(userId,course.getCourseId()); }

        if(exam.isEnabled())
        { exam.disable(); }
        else
        { throw new ExamAlreadyDisabledException(examId); }
    }
    
    /**
     * New features
     * @throws InstanceNotFoundException 
     * */
    private boolean isCourseMaster(Long userId, Long courseId)
            throws InstanceNotFoundException
    {
        Course course = courseDao.findById(courseId);
        return course.getAuthor().getUserId().equals(userId);
    }
    
    private boolean isLessonMaster(Long userId, Long lessonId) throws InstanceNotFoundException
    {
        Course c = null;
        boolean resp = false;
        Lesson less = null;
        //
        
        less = this.lessonDao.findById(lessonId);
        c = less.getCourse();
        resp =  ((c.getAuthor().getUserId()).equals(userId))?true:false;
        
        return resp;
    }
       
    private boolean isLessonStudent(Long userId, Long lessonId) throws InstanceNotFoundException
    {
        Lesson less = null;
        Examination slesson = null;
       
        slesson = examinationDao.find(lessonId, userId);
        if(slesson!=null)
        {
            return userId.equals(slesson.getEnrollment().getStudent().getUserId());
        }
        
        return false;
    }
    
    @Transactional(readOnly = true)
    public List<Enrollment> searchEnrollments(String query, 
                                                Long user, Long dept,
                                                Locale locale,
                                                Boolean finished, int startIndex, int count)
    {
        List<String> keywords = Query.parseQuery(query);
        return enrollmentDao.search(keywords, user, dept, locale, 
                                    finished, startIndex, count);
    }

    @Transactional(readOnly = true)
    public int numberOfEnrollments(String query, 
                                    Long user, Long dept,
                                    Locale locale, Boolean finished)
    {
        List<String> keywords = Query.parseQuery(query);
        return(int) enrollmentDao.numberOfEnrollments(keywords, user, dept, locale, finished);
    }
    
    public  Exam requestExam(long userId, long lessonId) 
            throws InstanceNotFoundException, ExamAlreadyReleasedException, 
                    PermissionDeniedException, NotFinishedAllTutorials
    {         
        Examination examination = examinationDao.find(lessonId, userId);
        List<Exam> exams = examination.getLesson().getExams();
      
        // TODO FIX this is very inefficient
        for(Tutorial t : examination.getLesson().getTutorials())
        {
            if(!markedTutorialDao.exists(userId, t.getTutorialId(), Mark.DONE))
            { throw new NotFinishedAllTutorials(); }          
        }
        
        if(examination.isFinished())
        { throw new PermissionDeniedException(); }

        if(examination.getReleasedExam().getState() == State.REQUESTED)
        { throw new ExamAlreadyReleasedException(); }
        
        // pick an exam
        Exam exam = exams.get(random.nextInt(exams.size()));
        examination.newExam(exam);
           
        return exam;
    }
    
   
    public void answerExam(long userId, long lessonId, String fileURL)
            throws InstanceNotFoundException, PermissionDeniedException
    {
        Examination examination = examinationDao.find(lessonId, userId);
    	ReleasedExam releasedExam = examination.getReleasedExam();
    	
    	if(releasedExam.getState() != State.REQUESTED)
    	{ throw new PermissionDeniedException(); }

    	examination.answer(fileURL);
    }
    

    public void evaluateExam(long examinationId, long masterId, 
                                Grade grade, String commentary)
            throws InstanceNotFoundException, PermissionDeniedException
    {
        User master = userDao.findById(masterId);
    	Examination examination = examinationDao.findById(examinationId);
    	Enrollment enrollment = examination.getEnrollment();
    	ReleasedExam releasedExam = examination.getReleasedExam();
    	Course course = examination.getLesson().getCourse();
    	
    	if(course.getAuthor() != master)
	    { throw new PermissionDeniedException(); }
    	
    	if(releasedExam.getState() != State.ANSWERED)
    	{ throw new PermissionDeniedException(); }
    	
   		enrollment.evaluate(examination, grade, commentary);

    	new AssignAward(examination.getStudent(),awardDao)
    	        .addUserAwardByCousesDone();
  }    
    
    
    @Transactional(readOnly = true)
    public List<Examination> listExaminations(long userId, long courseId, 
                                                User.Role role, ReleasedExam.State state,
                                                List<Long> lessons, 
                                                int startIndex, int count) 
            throws InstanceNotFoundException, PermissionDeniedException
    { 
        if(!isCourseMaster(userId, courseId) && role == User.Role.MASTERCOACH)
        { throw new PermissionDeniedException(); }
        
        if(isCourseMaster(userId, courseId) && role == User.Role.STUDENT)
        { throw new PermissionDeniedException(); }
        
        return examinationDao.listExaminations(userId, courseId, 
                                                role, state, lessons, 
                                                startIndex, count);
    }
    
    @Transactional(readOnly = true)
    public int numberOfExaminations(long userId, long courseId, 
                                    User.Role role, ReleasedExam.State state, 
                                    List<Long> lessons)
            throws InstanceNotFoundException, PermissionDeniedException
    { 
        if(!isCourseMaster(userId, courseId) && role == User.Role.MASTERCOACH)
        { throw new PermissionDeniedException(); }
        
        if(isCourseMaster(userId, courseId) && role == User.Role.STUDENT)
        { throw new PermissionDeniedException(); }
        
        return(int) examinationDao.numberOfExaminations(userId, courseId, 
                                                        role, state, lessons);
    }
}
    

 